package com.springboot.frame.key;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

import java.lang.management.ManagementFactory;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.util.concurrent.ThreadLocalRandom;

/**
 * 分布式全局唯一ID主键生成器
 *
 * @author liheng
 */
public class SnowFlakeKeyGenerator implements AutoKeyGenerator<Long> {

  /**
   * INSTANCE实例
   */
  public static final SnowFlakeKeyGenerator INSTANCE = new SnowFlakeKeyGenerator();

  /**
   * snowflake实例
   */
  private static final Snowflake SNOWFLAKE = new Snowflake();

  @Override
  public Long generateId() {
   return SNOWFLAKE.nextId();
  }


  @Slf4j
  private static class Snowflake {

    /* 时间起始标记点，作为基准，一般取系统的最近时间（一旦确定不能变动） */
    private final long twepoch = 1288834974657L;
    private final long workerIdBits = 5L;/* 机器标识位数 */
    private final long datacenterIdBits = 5L;
    private final long maxWorkerId = -1L ^ (-1L << workerIdBits);
    private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
    private final long sequenceBits = 12L;/* 毫秒内自增位 */
    private final long workerIdShift = sequenceBits;
    private final long datacenterIdShift = sequenceBits + workerIdBits;
    /* 时间戳左移动位 */
    private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
    //序列号最大值
    private final long sequenceMask = -1L ^ (-1L << sequenceBits);

    private long workerId;

    /* 数据标识id部分 */
    private long datacenterId;
    private long sequence = 0L;/* 0，并发控制 */
    //上次生产id时间戳 ，初始值为负数
    private long lastTimestamp = -1L;

    public Snowflake() {
      this.datacenterId = getDatacenterId(maxDatacenterId);
      this.workerId = getMaxWorkerId(datacenterId, maxWorkerId);
    }

    /**
     * 获取 maxWorkerId
     */
    private long getMaxWorkerId(long datacenterId, long maxWorkerId) {
      StringBuilder mpid = new StringBuilder();
      mpid.append(datacenterId);
      String name = ManagementFactory.getRuntimeMXBean().getName();
      if (StringUtils.isNotEmpty(name)) {
        // GET jvmPid
        mpid.append(name.split("@")[0]);
      }
      // MAC + PID 的 hashcode 获取16个低位
      return (mpid.toString().hashCode() & 0xffff) % (maxWorkerId + 1);
    }

    /**
     * 数据标识id部分
     */
    private long getDatacenterId(long maxDatacenterId) {
      long id = 0L;
      try {
        InetAddress ip = InetAddress.getLocalHost();
        NetworkInterface network = NetworkInterface.getByInetAddress(ip);
        if (network == null) {
          id = 1L;
        } else {
          byte[] mac = network.getHardwareAddress();
          id = ((0x000000FF & (long) mac[mac.length - 1]) | (0x0000FF00 & (
              ((long) mac[mac.length - 2]) << 8))) >> 6;
          id = id % (maxDatacenterId + 1);
        }
      } catch (Exception e) {
        log.warn(" getDatacenterId: " + e.getMessage());
      }
      return id;
    }

    /**
     * 获取下一个ID
     */
    public synchronized long nextId() {
      long timestamp = System.currentTimeMillis();
      // 闰秒
      if (timestamp < lastTimestamp) {
        long offset = lastTimestamp - timestamp;
        int insecond = 5;
        if (offset <= insecond) {
          try {
            wait(offset << 1);
            timestamp = System.currentTimeMillis();
            if (timestamp < lastTimestamp) {
              throw new RuntimeException(
                  String
                      .format("Clock moved backwards.  Refusing to generate id for %d milliseconds",
                          offset));
            }
          } catch (Exception e) {
            throw new RuntimeException(e);
          }
        } else {
          throw new RuntimeException(
              String.format("Clock moved backwards.  Refusing to generate id for %d milliseconds",
                  offset));
        }
      }

      if (lastTimestamp == timestamp) {
        // 相同毫秒内，序列号自增
        sequence = (sequence + 1) & sequenceMask;
        if (sequence == 0) {
          // 同一毫秒的序列数已经达到最大，即阻塞到下一个毫秒,获得新的时间戳
          timestamp = tilNextMillis(lastTimestamp);
        }
      } else {
        // // 时间戳改变，毫秒内序列重置，不同毫秒内，序列号置为 1 - 3 随机数
        sequence = ThreadLocalRandom.current().nextLong(1, 3);
      }

      // 将上次时间值刷新
      lastTimestamp = timestamp;

      return ((timestamp - twepoch) << timestampLeftShift) // 时间戳部分
          | (datacenterId << datacenterIdShift) // 数据中心部分
          | (workerId << workerIdShift) // 机器标识部分
          | sequence; // 序列号部分
    }


    /**
     * 阻塞到下一毫秒,获得新时间戳
     *
     * @param lastTimestamp 上次生成ID时间截
     * @return 当前时间戳
     */
    private long tilNextMillis(long lastTimestamp) {
      long timestamp = System.currentTimeMillis();
      while (timestamp <= lastTimestamp) {
        timestamp = System.currentTimeMillis();
      }
      return timestamp;
    }
  }

}
